// 11.2 关联容器概述
/**
 * 关联容器不支持顺序容器的位置相关的操作，例如push_front或push_back。
 * 原因是关联容器中元素是根据关键字存储的，这些操作对关联容器没有意义。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
using namespace std;

// 我们不能直接定义一个Sales_data的multiset，因为Sales_data没有<运算符。
// 但是，可以用10.3.1节练习（第345页）中的compareIsbn函数来定义一个multiset。
// 此函数在Sales_data对象的ISBN成员上定义了一个严格弱序。函数compareIsbn应该像下面这样定义[插图]
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{
    return lhs.isbn() < rhs.isbn();
}

// 返回一个pair的函数
pair<string, int> process(vector<string> &v)
{
    if (!v.empty())
    {
        return {v.back(), v.back().size()}; // 列表初始化
    }
    else
    {
        return pair<string, int>(); // 隐式构造返回值
    }
}

int main()
{
    // 11.2.1 定义关联容器
    // 在新标准下，我们也可以对关联容器进行值初始化：[插图][插图]
    map<string, size_t> word_cout;                                                                      // 空容器
    set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"}; // 列表初始化
    map<string, string> authors = {
        {"1", "one"},
        {"2", "two"},
        {"3", "three"},
    }; // 三个元素map

    // 初始化multimap或multiset
    // 容器multimap和multiset，它们都允许多个元素具有相同的关键字。
    vector<int> ivec;
    for (vector<int>::size_type i = 0; i != 10; ++i)
    {
        ivec.push_back(i);
        ivec.push_back(i); // 每个数重复保存一次
    }
    set<int> iset(ivec.cbegin(), ivec.cend());  // iset包含来自ivec的不重复的元素
    multiset miset(ivec.cbegin(), ivec.cend()); // miset包含所有20个元素
    cout << ivec.size() << endl;                // 打印20
    cout << iset.size() << endl;                // 打印10
    cout << miset.size() << endl;               // 打印20

    // 10.2.2 关键字类型的要求
    // 对于有序容器——map、multimap、set以及multiset，关键字类型必须定义元素比较的方法。
    // 默认情况下，标准库使用关键字类型的<运算符来比较两个关键字。

    // 有序容器的关键字类型
    // 在实际编程中，重要的是，如果一个类型定义了“行为正常”的<运算符，则它可以用作关键字类型。

    // 使用关键字类型的比较函数
    // 在尖括号中出现的每个类型，就仅仅是一个类型而已。
    // 当我们创建一个容器（对象）时，才会以构造函数参数的形式提供真正的比较操作（其类型必须与在尖括号中指定的类型相吻合）。
    // 为了使用自己定义的操作，在定义multiset时我们必须提供两个类型：
    // 1. 关键字类型Sales_data，
    // 2. 以及比较操作类型——应该是一种函数指针类型（参见6.7节，第221页）
    multiset<Sales_data, decltype(compareIsbn) *> bookstore(compareIsbn);
    // 此处，我们使用decltype来指出自定义操作的类型。
    // 记住，当用decltype来获得一个函数指针类型时，必须加上一个*来指出我们要使用一个给定函数类型的指针（参见6.7节，第223页）。
    // 用compareIsbn来初始化bookstore对象，这表示当我们向bookstore添加元素时，通过调用compareIsbn来为这些元素排序。
    // 即，bookstore中的元素将按它们的ISBN成员的值排序。
    // 可以用compareIsbn代替&compareIsbn作为构造函数的参数，因为当我们使用一个函数的名字时，在需要的情况下它会自动转化为一个指针（参见6.7节，第221页）。

    // 11.2.3 pair类型
    // 在介绍关联容器操作之前，我们需要了解名为pair的标准库类型，它定义在头文件utility中。
    // 一个pair保存两个数据成员。类似容器，pair是一个用来生成特定类型的模板。
    pair<string, string> anon;                   // 保存两个string
    pair<string, size_t> word_cout;              // 保存一个string和一个size_t
    pair<string, vector<int>> line;              // 保存string和vector<int>
    pair<string, string> author{"莫言", "余华"}; // 为每个成员提供初始化器

    // 创建pair对象的函数
    vector<string> v{"1","2","5"};
    process(v);

    return 0;
}